<?php

class DbAccessClassGenerator {

    private $arrFields;
    private $tableName;

    public static function createFiles(array $arrTables, $autoPath, $stubPath) {

        Dir::checkExistsAndReadable($autoPath);
        Dir::checkExistsAndReadable($stubPath);

        $tableAccessorsClassCode = 'class TableAccessors { ';

        foreach ($arrTables as $coord => $arrFields) {


            list($dbname, $tableName) = explode(':', $coord);
            if (!$tableName || !$dbname)
                throw new Exception("Invalid table coord: $coord");

            $generator = new DbAccessClassGenerator($tableName, $arrFields);

            File::save($autoPath . $generator->getAccessorClassName() . '.php', self::phpize($generator->genAccessor()));
            File::save($autoPath . 'Generated' . $generator->getRecordClassName() . '.php', self::phpize($generator->genRecord()));

            $stubFile = $stubPath . $generator->getRecordClassName() . '.php';
            if (!file_exists($stubFile)) {
                File::save($stubFile, self::phpize($generator->genRecordStub()));
            }

            $tableAccessorsClassCode .= $generator->getTableAccessorsMethod($dbname);
        }

        $tableAccessorsClassCode .= '}';
        File::save($autoPath . 'TableAccessors.php', self::phpize($tableAccessorsClassCode) );
    }

    private static function phpize($str) { return '<?php ' . preg_replace('~^\s*~m', '', $str) . ' ?>'; }

    private function __construct($tableName, array $arrFields) {
        $this->arrFields = $arrFields;
        $this->tableName = $tableName;
    }

    private function getAccessorClassName() { return $this->underline2camel($this->tableName) . 'Accessor'; }
    private function getRecordClassName()   { return $this->underline2camel($this->tableName) . 'Record'; }

    private function genAccessor() {

        $keyFieldName = strtoupper(reset($this->arrFields));

        $constDefinition = '';
        $constUsage = '';
        foreach ($this->arrFields as $field) {
            $constDefinition .= 'const ' . strtoupper($field) . ' = ' . $this->literal($field) . '; ';
            $constUsage      .= 'self::' . strtoupper($field) . ', ';
        }

        $recordClassName = $this->getRecordClassName();
        $accessorClassName = $this->getAccessorClassName();

        $class = "

            class $accessorClassName extends AbstractTableAccessor {

                $constDefinition

                protected function getFieldNames() {
                    return array($constUsage);
                }

                protected function getTableName() { return '$this->tableName'; }
                public function getKeyFieldName() { return self::$keyFieldName; }

                /**
                /* @return $recordClassName
                */
                public function createRecord(\$id = null) { return new $recordClassName(\$id); }

                /**
                /* @return $recordClassName
                */
                public function getRecord(\$id) { return \$this->getRecordByKeyField(\$id); }
            }
        ";

        return $class;
    }

    private function genRecordStub() {
        $recordClassName = $this->getRecordClassName();
        $class  = "    class $recordClassName extends Generated$recordClassName {\n";
        $class .= "        //TODO\n";
        $class .= "    }\n";

        return $class;
    }

    private function genRecord() {

        $recordClassName = $this->getRecordClassName();
        $accessorClassName = $this->getAccessorClassName();

        $getAccessorCode = 'TableAccessors::' . $this->getTableAccessorsMethodName() . '()';

        $gettersSetters = '';

        foreach ($this->arrFields as $field) {

            $fieldConst = $accessorClassName . '::' . strtoupper($field);
            $methodName = $this->underline2camel($field);

            if (preg_match('~date|time~i', $methodName)) {
                $gettersSetters .=  "
                    /**
                     * @return Date
                     */
                    public function get$methodName() { \$this->getDateTimeField($fieldConst); }
                    public function set$methodName(Date \$val) { \$this->getDateTimeField($fieldConst, \$val); }
                ";
            } else {
                $gettersSetters .=  "
                    public function get$methodName() { \$this->getField($fieldConst); }
                    public function set$methodName(\$val) { \$this->setField($fieldConst, \$val); }
                ";
            }
        }
        #echo $gettersSetters;

        $class = "
            class Generated$recordClassName extends AbstractTableRecord {

                /**
                 * @return $accessorClassName
                 */
                public function getAccessor() { return $getAccessorCode; }

                $gettersSetters

                protected function checkFieldsBeforeSave() {}
            }
        ";

        return $class;
    }

    private function getTableAccessorsMethodName() {
        $name = $this->underline2camel($this->tableName);
        $name[0] = strtolower($name[0]);
        return $name;
    }

    private function getTableAccessorsMethod($dbname) {
        $methodName = $this->getTableAccessorsMethodName();
        $accessorName = $this->getAccessorClassName();
        return "
            /**
             * @return $accessorName
             */
            public static function $methodName() {
                return TableAccessorsFactory::getDbAccessor('$accessorName', '$dbname');
            }
            ";
    }

    private function underline2camel($str) {
        #echo "=$str=\n";
        #echo "~" . implode('', array_map('strtolower', array_map('ucfirst', explode('_', $str) ) ) ) . "\n";
        return implode('', array_map('ucfirst', array_map('strtolower', explode('_', $str) ) ) );
    }

    private function literal($value) {
        return is_numeric($value) ?
            $value :
            "'" . strtr($value, array("'" => "\'")) . "'";
    }
}

?>